package com.atguigu.gmall.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.cart.client.CartFeignClient;
import com.atguigu.gmall.cart.model.CartInfo;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.HttpClientUtil;
import com.atguigu.gmall.common.util.SnowFlake;
import com.atguigu.gmall.enums.model.OrderStatus;
import com.atguigu.gmall.enums.model.PaymentWay;
import com.atguigu.gmall.enums.model.ProcessStatus;
import com.atguigu.gmall.order.model.OrderDetail;
import com.atguigu.gmall.order.model.OrderInfo;
import com.atguigu.gmall.order.mapper.OrderInfoMapper;
import com.atguigu.gmall.order.service.OrderDetailService;
import com.atguigu.gmall.order.service.OrderInfoService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.rabbit.config.MqConst;
import com.atguigu.gmall.rabbit.service.RabbitService;
import com.atguigu.gmall.user.client.UserFeignClient;
import com.atguigu.gmall.user.model.UserAddress;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.atguigu.gmall.common.constant.RedisConst.USER_CART_KEY_SUFFIX;
import static com.atguigu.gmall.common.constant.RedisConst.USER_KEY_PREFIX;

/**
 * 订单表 订单表 业务实现类
 *
 * @author atguigu
 * @since 2023-08-10
 */
@Slf4j
@Service
@SuppressWarnings("all")
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private CartFeignClient cartFeignClient;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private OrderDetailService orderDetailService;

    @Autowired
    private SnowFlake snowFlake;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ProductFeignClient productFeignClient;


    @Autowired
    private RabbitService rabbitService;

    /**
     * 第三方库存系统调用接口基础路径
     */
    @Value("${ware.url}")
    private String wareUrl;

    /**
     * 当前用户-汇总订单确认所需要数据
     * ${userAddressList}收件人地址列表
     * ${detailArrayList} 订单明细列表
     * ${totalNum} 商品数量
     * ${totalAmount} 订单总金额
     * ${tradeNo}-订单流水号
     *
     * @return
     */
    @Override
    public Map<String, Object> getOrderTadeData(Long userId) {
        Map<String, Object> mapResult = new HashMap<>();

        //1.远程调用用户服务获取用户收件地址列表
        CompletableFuture<Void> userAddressListCompletableFuture = CompletableFuture.runAsync(() -> {
            List<UserAddress> userAddressList = userFeignClient.getUserAddressListByUserId(userId);
            if (!CollectionUtils.isEmpty(userAddressList)) {
                mapResult.put("userAddressList", userAddressList);
            }
        }, threadPoolExecutor);

        //2.远程调用购物车服务获取选中购物车商品明细（订单明细）
        CompletableFuture<Void> orderDetailCompletableFuture = CompletableFuture.runAsync(() -> {
            List<OrderDetail> orderDetailList = cartFeignClient.getCartCheckedList(userId);
            if (!CollectionUtils.isEmpty(orderDetailList)) {
                mapResult.put("detailArrayList", orderDetailList);

                //3.基于订单明细计算总金额以及数量
                OrderInfo orderInfo = new OrderInfo();
                orderInfo.setOrderDetailList(orderDetailList);
                orderInfo.sumTotalAmount(); //计算总金额
                mapResult.put("totalAmount", orderInfo.getTotalAmount());
                mapResult.put("totalNum", orderDetailList.size());
            }
        }, threadPoolExecutor);

        CompletableFuture.allOf(userAddressListCompletableFuture, orderDetailCompletableFuture).join();

        //4.生成订单流水号-避免浏览器回退导致订单重复提交
        String tradeNo = this.generateTradeNo(userId);
        mapResult.put("tradeNo", tradeNo);
        return mapResult;
    }


    /**
     * 订单提交,跟后续支付业务对接
     *
     * @param orderInfo
     * @param tradeNo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long submitOrder(OrderInfo orderInfo, String tradeNo) {
        //TODO 业务校验
        //1. 验证用户是否存在使用浏览器回退导致订单重复提交-验证流水号
        // todo 为了避免判断跟删除之间发生异常 需要保证判断跟删除原子性
       /* Boolean flag = this.checkTradeNo(orderInfo.getUserId(), tradeNo);
        if (!flag) {
            throw new RuntimeException("订单重复提交！");
        }
        this.deleteTradeNo(orderInfo.getUserId());*/
        String key = RedisConst.USER_KEY_PREFIX + orderInfo.getUserId() + ":tradeno";
        String sciptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<Long>();
        redisScript.setScriptText(sciptText);
        redisScript.setResultType(Long.class);
        Long flag = (Long) redisTemplate.execute(redisScript, Arrays.asList(key), tradeNo);
        if (flag == 0) {
            throw new RuntimeException("订单重复提交！");
        }

        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            //2.0 声明多个异步任务异常集合-将来异步任务验证失败，将异常信息写入到异常集合中
            List<String> errorList = new ArrayList<>();
            //2.x声明存放异步任务集合
            List<CompletableFuture> completableFutures = new ArrayList<>();

            orderDetailList.forEach(orderDetail -> {
                //2. 远程调用第三方仓储服务-验证订单中订单明细商品库存是否充足
                CompletableFuture<Void> stockCompletableFuture = CompletableFuture.runAsync(() -> {
                    Boolean hasStock = this.checkStock(orderDetail.getSkuId(), orderDetail.getSkuNum());
                    if (!hasStock) {
                        //throw new RuntimeException("商品:" + orderDetail.getSkuName() + ",库存不足！");
                        errorList.add("商品:" + orderDetail.getSkuName() + ",库存不足！");
                    }
                }, threadPoolExecutor);
                completableFutures.add(stockCompletableFuture);

                //3. 远程调用商品服务-验证订单中订单明细商品价格是否变化
                CompletableFuture<Void> priceCompletableFuture = CompletableFuture.runAsync(() -> {
                    //3.1 先查询商品最新价格
                    BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
                    //3.2 比较订单明细价格跟数据库商品价格是否一致
                    if (orderDetail.getOrderPrice().compareTo(skuPrice) != 0) {
                        //3.3 如果价格不一致，提示错误信息同时，修改当前用户购物车商品价格
                        String cartKey = USER_KEY_PREFIX + orderInfo.getUserId() + USER_CART_KEY_SUFFIX;
                        BoundHashOperations<String, String, CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
                        String hashKey = orderDetail.getSkuId().toString();
                        if (hashOps.hasKey(hashKey)) {
                            CartInfo cartInfo = hashOps.get(hashKey);
                            cartInfo.setSkuPrice(skuPrice);
                            hashOps.put(hashKey, cartInfo);
                        }
                        //throw new RuntimeException("商品" + orderDetail.getSkuName() + ",价格发生变化，请重新提交订单！");
                        errorList.add("商品" + orderDetail.getSkuName() + ",价格发生变化，请重新提交订单！");
                    }
                }, threadPoolExecutor);
                completableFutures.add(priceCompletableFuture);

            });
            //4.保证所有异步任务都执行完毕
            CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()])).join();

            //5.判断异常集合是否有值 有值：某件商品验证失败
            if (!CollectionUtils.isEmpty(errorList)) {
                String errorMsg = errorList.stream().collect(Collectors.joining(","));
                throw new RuntimeException(errorMsg);
            }
        }
        //5.将订单以及订单明细保存
        Long orderId = this.saveOrder(orderInfo, "1");

        //发送延迟消息-延迟验证订单支付状态关闭订单
        rabbitService.sendDelayMsg(MqConst.EXCHANGE_DIRECT_ORDER_CANCEL, MqConst.ROUTING_ORDER_CANCEL, orderId, 30);

        //6.TODO 已经提交订单商品在购物车中清理（后续频繁进行提交订单）

        //TODO 将新增订单同步存入 “订单索引库”
        return orderId;
    }


    /**
     * 保存订单以及订单明细
     *
     * @param orderInfo
     * @param orderType "1":普通商城订单 "2":秒杀订单
     * @return
     */
    @Override
    public Long saveOrder(OrderInfo orderInfo, String orderType) {
        //1.封装订单对象中若干个属性
        //1.1 计算订单总金额
        orderInfo.sumTotalAmount();
        //1.2 订单状态 消费者  工作人员
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        //1.3 生成订单编号 后续支付发货重要字段 规则：SPH年月日雪花算法
        Date now = new Date();
        if ("1".equals(orderType)) {
            //普通商城订单编号
            String today = DateUtil.formatDate(now).replaceAll("-", "");
            long id = snowFlake.nextId();
            String outTradeNo = "SPH" + today + id;
            orderInfo.setOutTradeNo(outTradeNo);
        } else {
            //秒杀订单 订单编号，在秒杀服务中调用前赋值
        }
        //1.4 订单商品名称信息 商品图片
        String tradeBody = "";
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            String skuNameStr = orderDetailList.stream().map(OrderDetail::getSkuName).collect(Collectors.joining(","));
            if (skuNameStr.length() > 100) {
                tradeBody = skuNameStr.substring(0, 100); //对接支付宝支付，如果订单中名称太长会导致调用支付接口失败
            } else {
                tradeBody = skuNameStr;
            }
            orderInfo.setTradeBody(tradeBody);
            orderInfo.setImgUrl(orderDetailList.get(0).getImgUrl());
        }
        //1.5 过期时间 普通商城订单 1小时不支付，自动关闭订单  秒杀订单：5分钟
        Calendar calendar = Calendar.getInstance();
        if ("1".equals(orderType)) {
            calendar.add(Calendar.HOUR, 1);
        }
        if("2".equals(orderType)){
            calendar.add(Calendar.MINUTE, 5);
        }
        orderInfo.setExpireTime(calendar.getTime());
        //1.4 其他属性封装
        orderInfo.setPaymentWay(PaymentWay.ONLINE.name());
        orderInfo.setOperateTime(now);
        orderInfo.setProvinceId(1L);
        //2.执行订单保存
        this.save(orderInfo);
        Long orderId = orderInfo.getId();

        //3.封装订单明细集合
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            orderDetailList.stream().forEach(orderDetail -> {
                orderDetail.setOrderId(orderId);
                if("1".equals(orderType)){
                    orderDetail.setSourceType("MALL");
                    orderDetail.setSourceId(1L);
                }
                if("2".equals(orderType)){
                    orderDetail.setSourceType("SECKILL");
                    orderDetail.setSourceId(2L);
                }

            });
            //4.批量保存订单明细集合
            orderDetailService.saveBatch(orderDetailList);
        }
        return orderId;
    }

    /**
     * 生成流水号
     *
     * @param userId
     * @return
     */
    @Override
    public String generateTradeNo(Long userId) {
        //1.未当前购买用户生成流水号Key
        String key = RedisConst.USER_KEY_PREFIX + userId + ":tradeno";
        String tradeNo = UUID.randomUUID().toString().replaceAll("-", "");
        //2.将流水号存入Redis
        redisTemplate.opsForValue().set(key, tradeNo, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.MINUTES);
        return tradeNo;
    }

    /**
     * 验证流水号是否一致
     *
     * @param userId
     * @param tradeNo
     * @return
     */
    @Override
    public Boolean checkTradeNo(Long userId, String tradeNo) {
        String key = RedisConst.USER_KEY_PREFIX + userId + ":tradeno";
        String redisTradeNo = (String) redisTemplate.opsForValue().get(key);
        //1.2 判断用户提交流水号跟Redis存放是否一致
        return tradeNo.equals(redisTradeNo);
    }

    /**
     * 删除业务流水号
     *
     * @param userId
     */
    @Override
    public void deleteTradeNo(Long userId) {
        String key = RedisConst.USER_KEY_PREFIX + userId + ":tradeno";
        redisTemplate.delete(key);
    }


    /**
     * 调用第三方仓库系统验证商品在仓库中是否库存充足
     *
     * @param skuId 商品ID
     * @param num   数量
     * @return
     */
    @Override
    public Boolean checkStock(Long skuId, int num) {
        //http://localhost:9001/hasStock?skuId=20&num=7
        String hasStock = HttpClientUtil.doGet(wareUrl + "/hasStock?skuId=" + skuId + "&num=" + num);
        return "1".equals(hasStock);
    }

    /**
     * 分页查询当前用户订单
     *
     * @param page
     * @param limit
     * @return
     */
    @Override
    public Page<OrderInfo> getOrderList(String userId, int page, int limit) {
        //1、方式一：先根据用户ID分页获取订单列表，遍历订单分别获取每个订单订单明细列表
        //2、方式二：编写动态SQL获取订单列表（包含订单明细）
        Page<OrderInfo> infoPage = new Page<>(page, limit);
        //调用动态SQL传递MP提供分页对象Page，自动拼接分页SQL limit ?,?
        infoPage = baseMapper.getOrderList(infoPage, userId);
        return infoPage;
    }

    /**
     * 执行订单状态验证以及订单关闭
     *
     * @param orderId
     */
    @Override
    public void execExpireOrder(Long orderId) {
        //1.根据订单ID查询订单信息得到订单状态
        OrderInfo orderInfo = this.getById(orderId);
        if (orderInfo != null) {
            //2.判断订单状态 如果是未支付，将订单关闭
            if (ProcessStatus.UNPAID.name().equals(orderInfo.getOrderStatus())) {
                this.updateOrderStatus(orderId, ProcessStatus.CLOSED);

                //3.关闭订单后立即发送MQ消息通知支付系统关闭交易记录
                rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_PAYMENT_CLOSE, MqConst.ROUTING_PAYMENT_CLOSE, orderInfo.getOutTradeNo());
            }
        }

    }

    /**
     * 复用方法，将指定订单修改为指定状态
     *
     * @param orderId
     * @param processStatus
     */
    @Override
    public void updateOrderStatus(Long orderId, ProcessStatus processStatus) {
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setId(orderId);
        orderInfo.setProcessStatus(processStatus.name());
        orderInfo.setOrderStatus(processStatus.getOrderStatus().name());
        this.updateById(orderInfo);
    }

    /**
     * 发送MQ消息通知库存系统锁定库存
     * 要求：必须要严格按照库存系统接口要求提交参数
     *
     * @param orderId
     */
    @Override
    public void sendLockStockMsg(Long orderId) {
        //1.根据订单ID查询订单信息以及明细信息
        OrderInfo orderInfo = this.getOrderInfo(orderId);
        if (orderInfo != null) {
            //2.按照接口要求封装库存系统所需MQ参数
            Map<String, Object> mapResult = initWareMap(orderInfo);
            rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_WARE_STOCK, MqConst.ROUTING_WARE_STOCK, JSON.toJSONString(mapResult));
        }
    }

    /**
     * 根据订单以及订单明细构建仓库系统所需参数（扣减库存，拆单结果都需要复用该方法）
     *
     * @param orderInfo
     * @return
     */
    private Map<String, Object> initWareMap(OrderInfo orderInfo) {
        Map<String, Object> mapResult = new HashMap<>();
        //1.构建订单相关字段
        mapResult.put("orderId", orderInfo.getId());
        mapResult.put("consignee", orderInfo.getConsignee());
        mapResult.put("consigneeTel", orderInfo.getConsigneeTel());
        mapResult.put("orderComment", orderInfo.getOrderComment());
        mapResult.put("orderBody", orderInfo.getTradeBody());
        mapResult.put("deliveryAddress", orderInfo.getDeliveryAddress());
        mapResult.put("paymentWay", PaymentWay.ONLINE.name().equals(orderInfo.getPaymentWay()) ? 1 : 2);
        mapResult.put("wareId", orderInfo.getWareId());

        //2.循环遍历订单明细构建订单明细对象相关字段
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            List<Map<Object, Object>> orderDetailMapList = orderDetailList.stream().map(orderDetail -> {
                Map<Object, Object> detailMap = new HashMap<>();
                detailMap.put("skuId", orderDetail.getSkuId());
                detailMap.put("skuName", orderDetail.getSkuName());
                detailMap.put("skuNum", orderDetail.getSkuNum());
                return detailMap;
            }).collect(Collectors.toList());
            mapResult.put("details", orderDetailMapList);
        }
        return mapResult;
    }

    /**
     * 根据订单ID查询订单信息(订单明细)
     *
     * @param orderId
     * @return
     */
    @Override
    public OrderInfo getOrderInfo(Long orderId) {
        OrderInfo orderInfo = this.getById(orderId);
        if (orderInfo != null) {
            LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(OrderDetail::getOrderId, orderId);
            List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);
            orderInfo.setOrderDetailList(orderDetailList);
        }
        return orderInfo;
    }


    /**
     * 根据库存系统反馈进行拆单
     * 本质：将参数wareSkuMap中集合中元素有几个 新增几个子订单 skuIds中有几件商品就有几个订单明细
     *
     * @param orderId    订单ID
     * @param wareSkuMap 仓库跟商品Sku列表映射关系 [{"wareId":"1","skuIds":["2","10"]},{"wareId":"2","skuIds":["3"]}]
     * @return
     */
    @Override
    public String orderSplit(Long orderId, String wareSkuMap) {
        log.info("【订单服务】接收库存提交拆单参数：订单id：{}，仓库跟sku映射关系：{}", orderId, wareSkuMap);
        //1.将接收到参数wareSkuMap转为List集合 泛型：Map
        if (StringUtils.isNotBlank(wareSkuMap)) {
            List<Map> wareSkuMapList = JSON.parseArray(wareSkuMap, Map.class);

            //2.获取原始订单以及订单明细（为后续新增子订单提供基础数据）
            OrderInfo originOrderInfo = this.getOrderInfo(orderId);
            List<OrderDetail> originOrderOrderDetailList = originOrderInfo.getOrderDetailList();

            //3.遍历仓库跟SKU映射Map构建新订单以及订单明细并且将其保存（拆单）--每遍历一次就会产生新子订单
            List<OrderInfo> subOrderInfoList = wareSkuMapList.stream().map(wareSkuIdMap -> {
                //3.1.获取订单对应出货仓库ID
                String wareId = (String) wareSkuIdMap.get("wareId");
                //3.2.获取仓库对应商品skuId列表
                List<String> skuIds = (List<String>) wareSkuIdMap.get("skuIds");

                //3.3. 构建新子订单对象-从原始订单中拷贝给属性赋值
                OrderInfo subOrderInfo = new OrderInfo();
                BeanUtils.copyProperties(originOrderInfo, subOrderInfo);
                //3.3.1 为新子订单属性某些属性进行手动赋值 主键、总金额、父订单ID
                subOrderInfo.setId(null); //手动将主键改为空
                subOrderInfo.setParentOrderId(originOrderInfo.getId());
                //因为相应结果时候必须要指定订单对应出库仓库ID 在这里设置 为新订单关联仓库ID
                subOrderInfo.setWareId(wareId);

                //3.3.2 找打当前新子订单包含哪些商品 将原始子订单集合进行过滤获取当前新子订单中订单明细商品
                List<OrderDetail> subOrderDetailList = originOrderOrderDetailList.stream().filter(originOrderDetail -> {
                    return skuIds.contains(originOrderDetail.getSkuId().toString());
                }).collect(Collectors.toList());
                subOrderInfo.setOrderDetailList(subOrderDetailList);
                subOrderInfo.sumTotalAmount();

                //3.4 将新订单以及订单明细进行保存
                //3.4.1 保存订单
                this.save(subOrderInfo);
                Long subOrderId = subOrderInfo.getId();

                //3.4.2 保存订单明细
                subOrderDetailList.forEach(orderDetail -> {
                    orderDetail.setOrderId(subOrderId);
                });
                orderDetailService.saveBatch(subOrderDetailList);
                return subOrderInfo;
            }).collect(Collectors.toList());

            //4.修改原始订单状态为“SPIT”
            this.updateOrderStatus(orderId, ProcessStatus.SPLIT);

            //5.按照仓库系统要求进行拆单接口响应（注意：wareId 每个订单对应出货仓库id设置）
            List<Map<String, Object>> collect = subOrderInfoList.stream().map(subOrderInfo -> {
                Map<String, Object> mapResult = this.initWareMap(subOrderInfo);
                return mapResult;
            }).collect(Collectors.toList());

            return JSON.toJSONString(collect);
        }
        return null;
    }

    /**
     * 保存秒杀订单
     *
     * @param orderInfo
     * @return
     */
    @Override
    public Long submitSeckillOrder(OrderInfo orderInfo) {
        //调用保存订单方法
        return this.saveOrder(orderInfo, "2");
    }
}
